/*******************************************************************************************
 * Copyright (c) 2006-2009 Alex Robson, Rob Simmons, Craig Israel, and Rachel Twyford
 *
 * Use of this software in any form requires that you have both 
 * read and agreed to the following terms:
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the "Software"), 
 * to deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions: 
 *
 * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice, 
 * this list of conditions and the following disclaimer in the 
 * documentation and/or other materials provided with the distribution.
 * Neither the name of Nvigorate nor the names of its contributors may 
 * be used to endorse or promote products derived from this software without 
 * specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
 * DISCLAIMED. IN NO EVENT SHALL ANY CONTRIBUTOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ****************************************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;

namespace Nvigorate.Common.Reflection
{
	public sealed partial class Reflector
	{
		#region Public Static Generic Reflector Functions
		
		/// <summary>
		/// Dynamically invokes a generically typed function on the target target.
		/// </summary>
		/// <param name="target">The target target to invoke the method on</param>
		/// <param name="methodName">The name of the method to invoke</param>
		/// <param name="arguments">An object array containing th arguments to pass. Note:
		/// arguments must be in the array in the order they are required in the function.</param>
		/// <param name="genericArguments">The type to pass as the type parameter to the generic
		/// function</param>
		/// <returns>The result of the invoked method</returns>
        public static object CallMethod(object target, string methodName, object[] arguments, params Type[] genericArguments)
		{
		    return CallMethod(target, target.GetType(), methodName, arguments, genericArguments);
		}
		
		/// <summary>
		/// Dynamically invokes a generically typed static function on the target type.
		/// </summary>
		/// <param name="type">The target type to invoke the method on</param>
		/// <param name="methodName">The name of the static method to invoke</param>
		/// <param name="arguments">An object array containing th arguments to pass. Note:
		/// arguments must be in the array in the order they are required in the function.</param>
		/// <param name="genericArguments">The type to pass as the type parameter to the generic
		/// function</param>
		/// <returns>The result of the invoked method</returns>
        public static object CallMethod(Type type, string methodName, object[] arguments, params Type[] genericArguments)
		{
            return CallMethod(null, type, methodName, arguments, genericArguments);
		}
		
        /// <summary>
        /// Will attempt to determine the element type in the collection
        /// </summary>
        /// <param name="collection">The collection to get the element type from</param>
        /// <returns>The element type of the collection or object if one doesn't exist</returns>
        public static Type GetCollectionElementType(object collection)
        {
            if (IsCollection(collection))
            {                
                Type collectionType = collection.GetType();

                if(collectionType.BaseType.Equals(typeof(Array)))
                {
                    return collectionType.GetElementType();
                }
                else
                {
                    Type[] args = collectionType.GetGenericArguments();
                    if (args.Length > 0)
                        return args[0];
                    return collectionType; 
                }
            }
            return typeof (object);
        }

		/// <summary>
		/// Creates a generic List of type T from the given object array.
		/// </summary>
		/// <typeparam name="T">The type to make the generic list</typeparam>
		/// <param name="array">The array of ects to insert into the list</param>
		/// <returns>A generic list of type T containing the ects in array</returns>
		public static List<T> GetGenericListFromArray<T>(object[] array)
		{
			try
			{
			    var list = array.Cast<T>().ToList();
			    return list;
			}
			catch(Exception ex)
			{
				throw new ReflectorException(ex, "Could not create a generic List from the array. ",
						"This error usually results when an array contains an object of type not equal to {0} ",
						 typeof(T).Name);
			}
            return new List<T>();
		}
		
		/// <summary>
		/// Returns the first generic type argument of the member name within the class type
		/// </summary>
		/// <param name="type">The class type the field is defined in</param>
		/// <param name="memberName">The name of generic member to obtain the type argument from</param>
		/// <returns>The type argument used in the specified member name</returns>
		public static Type GetGenericTypeArgument(Type type, string memberName)
		{
			Type				typeArgument	= null;
			Type				memberType		= null;
			
			if (HasField(type, memberName))
			{
				memberType = GetFieldInfo(type, memberName, recursive).FieldType;
			}
			else if (HasProperty(type, memberName))
			{
				memberType = GetPropertyInfo(type, memberName, recursive).PropertyType;
			}
			else
			{
                throw new ReflectorException("Could not get the generic type of {0} becuase it does not exist for type {1} ",
                                            memberName, type.FullName);
			}
			
			if (memberType.IsGenericType)
			{
				typeArgument = memberType.GetGenericArguments()[0];
			}
			
			return typeArgument;
		}

        /// <summary>
        /// Returns the generic type arguments of the member name within the class type
        /// </summary>
        /// <param name="type">The class type the member is defined in</param>
        /// <param name="memberName">The name of generic member to obtain the type arguments from</param>
        /// <returns>The type arguments used in the specified member name</returns>
		public static List<Type> GetGenericTypeArguments(Type type, string memberName)
		{
            var typeArguments = new List<Type>();
			Type				memberType		= null;
			
			if (HasField(type, memberName))
			{
				memberType = GetFieldInfo(type, memberName, recursive).FieldType;
			}
			else if(HasProperty(type, memberName))
			{
				memberType = GetPropertyInfo(type, memberName, recursive).PropertyType;
			}
			else
			{
                throw new ReflectorException("Could not get the generic type of {0} becuase it does not exist for type {1} ",
                                            memberName, type.FullName);
			}
			
			if (memberType.IsGenericType)
			{
				typeArguments = new List<Type>(memberType.GetGenericArguments());
			}
			
			return typeArguments;
		}
		
        /// <summary>
        /// Creates an instance of a generic type using the type arguments.
        /// </summary>
        /// <param name="type">The type of generic class to create an instance of</param>
        /// <param name="typeArguments">The ordered array of type arguments to use in creating the instance</param>
        /// <returns>A typed generic instance</returns>
        /// <remarks>Generic arguments must appear in the correct order</remarks>
        public static object MakeGenericInstance(Type type, params Type[] typeArguments)
        {
			return Activator.CreateInstance(type.MakeGenericType(typeArguments));
        }

        /// <summary>
        /// Creates an instance of a generic type using the constructor arguments and type arguments.
        /// </summary>
        /// <param name="type">The type of generic class to create an instance of</param>
        /// <param name="typeArguments">The ordered array of type arguments to use in creating the instance</param>
        /// /// <param name="constructorArgs">The ordered arguments to pass to the constructor of the class type</param>
        /// <returns>A typed generic instance</returns>
        /// <remarks>Both generic and constructor arguments must appear in the correct order</remarks>
        public static object MakeGenericInstance(Type type, Type[] typeArguments, params object[] constructorArgs)
        {
			return Activator.CreateInstance(type.MakeGenericType(typeArguments), constructorArgs);
        }

        /// <summary>
        /// Takes the provided class type and provides a generic type definition using the type arguments.
        /// </summary>
        /// <param name="type">The class type to get a generic version of</param>
        /// <param name="typeArguments">The type arguments to use for creating the generic type</param>
        /// <returns>A generic type definition of the provided type</returns>
		public static Type MakeGenericType(Type type, params Type[] typeArguments)
		{
			return type.MakeGenericType(typeArguments);
		}
		
		#endregion	
	}
}
